/**
 * @fileoverview
 * This script defines a widget for finding Drosophila melanogaster genes via FlyBase.
 * @author <a href="http://purl.org/net/aliman">Alistair Miles</a>
 * @version $Revision:539 $ on $Date: 2008-08-13 16:09:30 +0100 (Wed, 13 Aug 2008) $ by $Author: aliman $
 * @requires YAHOO.log
 * @requires YAHOO.lang.JSON
 * @requires YAHOO.util.Dom
 * @requires YAHOO.util.Connect
 * @requires YAHOO.util.CustomEvent
 * @requires flyui.util
 * @requires flyui.flybase
 * TODO license terms
 */
 
 
// create a namespace if not already defined
flyui.namespace("flyui.genefinder");


/** 
 * Create a new widget instance.
 * @class
 * A widget for finding and displaying D. melanogaster genes from FlyBase.
 * @constructor
 * @param {flyui.flybase.Service} service the service to use to retrieve data from
 * @param {Object} renderer the renderer to use with this widget instance (see also flyui.genefinder.DefaultRenderer)
 */
flyui.genefinder.Widget = function( service, renderer ) {
	
	var that = this;
	
	/** @private */
	this._controller = null;

	/**
	 * @private
	 * @type flyui.mvcutils.GenericModel2
	 */	
	this._model = null;
	
	/**
	 * @private
	 */
	this._renderer = renderer;
	
	/**
	 * @private
	 */
	this._geneSelectedEvent = new YAHOO.util.CustomEvent("GENESELECTED", this);

	/**
	 * @private
	 */
	this._genesFoundEvent = new YAHOO.util.CustomEvent("GENESFOUND", this);
	
	/**
	 * TODO doc me
	 * @private
	 */
	this._init = function() {
		// create a model
		this._model = new flyui.mvcutils.GenericModel2();
		this._model.setDefinition(flyui.genefinder.modelDefinition);
		
		// instantiate the controller
		this._controller = new flyui.genefinder.Controller(this._model, service, this);
		
		// connect the renderer to the model
		this._renderer.connect(this._model);
		
		// instantiate a user event handler and pass to renderer
		this._renderer.setUserEventHandler(new flyui.genefinder.UserEventHandler(this._controller));
	};
	
//	// DOM event handlers
//	this.makeOnResultClickedHandler = function(index) {
//		return function(event) {
//			log("received click event, call the controller to set selection: "+index);	
//			that._controller.setSelectionIndex(index);
//		};
//	}
	
	// DOM event handlers
//	this.onResultClicked = function( event, index ) {
//		log("received click event, call the controller to set selection: "+index);	
//		that._controller.setSelectionIndex(index);
//	}

	// do initialisation
	this._init();
		
	/**
	 * Subscribe to a custom event.
	 * @param type the type of the event
	 * @param listener the callback function
	 */
	this.subscribe = function(type, listener, obj) {
		if (type == "GENESELECTED") {
			this._geneSelectedEvent.subscribe(listener, obj);
		}
		else if (type == "GENESFOUND") {
			this._genesFoundEvent.subscribe(listener, obj);
		}
	};
	
};


/**
 * TODO doc me
 */
flyui.genefinder.Widget.prototype.findGenesByAnyName = function( anyName, caseSensitive ) {
	// pass through to controller
	this._controller.findGenesByAnyName(anyName, caseSensitive);
};


/**
 * TODO doc me
 */
flyui.genefinder.Widget.prototype.setSelectionIndex = function( index ) {
	// pass through to controller
	this._controller.setSelectionIndex(index);
}

/**
 * TODO doc me
 */
flyui.genefinder.UserEventHandler = function( controller ) {

	this._onResultClicked = function( event, index ) {
		log("received click event, call the controller to set selection: "+index);	
		controller.setSelectionIndex(index);
	}	
	
};





/** 
 * Create a new controller instance.
 * @class
 * Controller class for the flyui.genefinder widget.
 * @constructor
 * @param {flyui.mvcutils.GenericModel2} model the model to use to store data
 * @param {flyui.flybase.Service} service the service to use to retrieve data from
 * @param {flyui.genefinder.Widget} widget the widget for which this is the controller
 */
flyui.genefinder.Controller = function( model, service, widget ) {
	
	var that = this;
	
	/**
	 * @private
	 */
	this._model = model;

	/**
	 * @private
	 */
	this._service = service;

	/**
	 * @private
	 */
	this._parent = widget;
	
	
	/**
	 * TODO doc me
	 * @private
	 */
	this._findGenesByAnyNameSuccess = function( genes ) {

		log("request success");
		
		// set the results
		that._model.set("RESULTS", genes);
		
		// set model state
		that._model.set("STATE", "READY");
		
		// fire event
		log("firing GENESFOUND event");
		var event = that._parent._genesFoundEvent;
//			log("event: "+event, "debug");
		event.fire(genes);

		// auto-select if only one result
		if (genes.length == 1) {
			that.setSelectionIndex(0);
		}
		
	}
	
	
	/**
	 * TODO doc me
	 * @private
	 */
	this._findGenesByAnyNameFailure = function( response ) {
		
		log("request failed, status "+response.status+" "+response.statusText, "error");
		
		// set the error message
		var msg = "There was an error retrieving data from FlyBase, see the logs for more info. The server may be busy or down, please try again later. If this message persists, please contact the Image Bioinformatics Research Group at bioimage@mail.ontonet.org.";		
		that._model.set("ERRORMESSAGE", msg);

		// set the state
		that._model.set("STATE", "SERVERERROR");		
	}
	
};


/**
 * Find D. melanogaster genes by any name (including symbols, flybase IDs, synonyms).
 * @param {String} anyName any name, symbol or synonym
 * @param {String} caseSensitive
 */
flyui.genefinder.Controller.prototype.findGenesByAnyName = function( anyName, caseSensitive ) {
	log("findGenesByAnyName: "+anyName);

	log("success callback: "+this._findGenesByAnyNameSuccess, "debug");
	
	// pass through to private method
	this._findGenesByAnyName(anyName, this._findGenesByAnyNameSuccess, this._findGenesByAnyNameFailure, caseSensitive);
};


/**
 * Implementation of findGenesByAnyName, allowing insertion of custom callbacks for testing.
 * @param {String} anyName any name, symbol or synonym
 * @param {Function} success callback
 * @param {Function} failure callback
 * @param {boolean} caseSensitive
 * @private
 */
flyui.genefinder.Controller.prototype._findGenesByAnyName = function( anyName, success, failure, caseSensitive ) {
	log("_findGenesByAnyName: "+anyName, "debug");
	
	log("set state pending", "debug");
	this._model.set("STATE", "PENDING");
	
	log("set model property query", "debug");
	this._model.set("QUERY", anyName);
	
	log("kick off request", "debug");
	if (caseSensitive) {
		this._service.findGenesByAnyName(anyName, success, failure);
	} else {
		this._service.findGenesByAnyNameCaseInsensitive(anyName, success, failure);
	}
};


/**
 * TODO doc me
 */
flyui.genefinder.Controller.prototype.setSelectionIndex = function( index ) {
	log("setSelectionIndex, index: "+index, "debug");
	var results = this._model.get("RESULTS");
	if (results != null) {
		if (index >= 0 && index < results.length) {
			this._model.set("SELECTIONINDEX", index);
//			log("widget: "+this._parent, "debug");
			var event = this._parent._geneSelectedEvent;
//			log("event: "+event, "debug");
			event.fire(results[index]);
		}
		else {
			throw {name:"flyui.genefinder.SelectionOutOfBounds", message:"index "+index+" cannot apply to results length "+results.length};
		}
	}
};


/** 
 * Create a new renderer instance.
 * @class
 * A default renderer for the GeneFinder Widget.
 */
flyui.genefinder.DefaultRenderer = function() {};


/** 
 * @type Element
 * @private */
flyui.genefinder.DefaultRenderer.prototype._canvas = null;

/**
 * @type Element
 * @private 
 */
flyui.genefinder.DefaultRenderer.prototype._pendingPane = null;

/**
 * @type Element
 * @private 
 */
flyui.genefinder.DefaultRenderer.prototype._resultsSummaryPane = null;

/**
 * @type Element
 * @private 
 */
flyui.genefinder.DefaultRenderer.prototype._resultsPane = null;

/**
 * @type Element
 * @private 
 */
flyui.genefinder.DefaultRenderer.prototype._messagePane = null;

/**
 * @type flyui.genefinder.UserEventHandler
 * @private
 */
flyui.genefinder.DefaultRenderer.prototype._userEventHandler = null;

	
/**
 * Set the DOM element to which this renderer applies and initialise it.
 * @param {Element} canvas
 */
flyui.genefinder.DefaultRenderer.prototype.setCanvas = function( canvas ) {
	this._canvas = canvas;
	this._initCanvas();
};


/**
 * Set the user event handler.
 * @param flyui.genefinder.UserEventHandler handler
 */
flyui.genefinder.DefaultRenderer.prototype.setUserEventHandler = function( handler ) {
	this._userEventHandler = handler;
};


/**
 * Initialise the main components of the widget's DOM
 * @private
 */
flyui.genefinder.DefaultRenderer.prototype._initCanvas = function() {

	// setup pending pane
	this._pendingPane = document.createElement("p");
	this._pendingPane.innerHTML = "pending...";
	this._canvas.appendChild(this._pendingPane);
	flyui.mvcutils.hide(this._pendingPane);

	// setup results summary pane
	this._resultsSummaryPane = document.createElement("p");
	this._resultsSummaryPane.innerHTML = "this should never be displayed";
	this._canvas.appendChild(this._resultsSummaryPane);
	flyui.mvcutils.hide(this._resultsSummaryPane);
	
	// setup results pane
	this._resultsPane = document.createElement("div");
	this._canvas.appendChild(this._resultsPane);
	flyui.mvcutils.hide(this._resultsPane);

	// message pane
	this._messagePane = document.createElement("p");
	this._messagePane.innerHTML = "this should never be displayed";
	this._canvas.appendChild(this._messagePane);
	flyui.mvcutils.hide(this._messagePane);	
};


/**
 * TODO doc me
 * @private
 */
flyui.genefinder.DefaultRenderer.prototype._onStateChanged = function( from, to ) {

	if ( to == "PENDING" ) {
		flyui.mvcutils.show(this._pendingPane);
		flyui.mvcutils.hide(this._messagePane);
		flyui.mvcutils.hide(this._resultsSummaryPane);
		flyui.mvcutils.hide(this._resultsPane);
	}
	else if ( to == "READY" ) {
		flyui.mvcutils.hide(this._pendingPane);
		flyui.mvcutils.show(this._messagePane);
		flyui.mvcutils.show(this._resultsSummaryPane);
		flyui.mvcutils.show(this._resultsPane);			
	} 
	else if ( to == "SERVERERROR" || to == "UNEXPECTEDERROR" ) {
		flyui.mvcutils.hide(this._pendingPane);
		flyui.mvcutils.show(this._messagePane);
		flyui.mvcutils.hide(this._resultsSummaryPane);
		flyui.mvcutils.hide(this._resultsPane);			
	} 
	else {
		// this should never happen
		throw {name:"flyui.genefinder.UnexpectedStateError", message:"Invalid state: "+newVal};
	}
	
};


/**
 * TODO doc me
 * @private
 */
flyui.genefinder.DefaultRenderer.prototype._onQueryChanged = function( from, to ) {
	// store the query, do nothing else
	this._query = to;
};


/**
 * TODO doc me
 * @private
 */
flyui.genefinder.DefaultRenderer.prototype._onResultsChanged = function( from, to ) {
	
	// temporarily store the results
	this._results = to;
	
	// set the results summary
	this._renderResultsSummary(this._query, to.length);
	
	if (to.length > 0) {
		
		// empty the results pane
		this._resultsPane.innerHTML = "";	

		// set the content
		var resultsNode = this._renderResults(to);
		log("results node content:"+resultsNode.innerHTML, "debug");
		this._resultsPane.appendChild(resultsNode);	
		
		// set the message pane content
		this._messagePane.innerHTML = "selected gene: <strong>please select one of the genes from the list above, or try another query</strong>";

	}
	else {
		this._resultsPane.innerHTML = "";
		this._messagePane.innerHTML = "";
	}

};


/**
 * TODO doc me
 * @private
 */
flyui.genefinder.DefaultRenderer.prototype._onSelectionIndexChanged = function( from, to ) {
	log("onSelectionIndexChanged, from:"+from+", to:"+to, "debug");
	
	// get the result nodes
	var nodes = this._getResultNodes(); // that._resultsPane.getElementsByTagName("tr");
	
	if (from != null) {
		// remove class
		YAHOO.util.Dom.removeClass(nodes[from+1], "selected");
	}
	
	if (to != null) {
		// add class
		log("adding selected class to node: "+nodes[to+1]+"debug");
		YAHOO.util.Dom.addClass(nodes[to+1], "selected");
		var gene = this._results[to];
		this._messagePane.innerHTML = "selected gene: <strong>"+gene.symbols.join(" / ")+"</strong> [<a href='http://flybase.org/reports/"+gene.flybaseID+".html'>FlyBase Gene Report</a>]";
	}	
};


flyui.genefinder.DefaultRenderer.prototype._getResultNodes = function() {
	return this._resultsPane.getElementsByTagName("tr");
}


/**
 * TODO doc me
 * @private
 */
flyui.genefinder.DefaultRenderer.prototype._onErrorMessageChanged = function( from, to ) {
	this._messagePane.innerHTML = to;
}


/**
 * TODO doc me
 * @private
 */
flyui.genefinder.DefaultRenderer.prototype._onModelChanged = function(type, args, self) {
	var handlers = {
		"STATE":"_onStateChanged",
		"QUERY":"_onQueryChanged",
		"RESULTS":"_onResultsChanged",
		"ERRORMESSAGE":"_onErrorMessageChanged",
		"SELECTIONINDEX":"_onSelectionIndexChanged"
	}
	var handler = handlers[type];
	// call the handler
	self[handler](args[0], args[1]);
}


/**
 * TODO doc me
 */
flyui.genefinder.DefaultRenderer.prototype.connect = function( model ) {
		
	model.subscribeAll(this._onModelChanged, this);

}


/**
 * Helper function for building DOM elements containing lists of elements
 * @param {string} outerTag outer tag (e.g. "TR") 
 * @param {string} innerTag inner tag (e.g. "TD") 
 * @param {string*} list of inner HTML values to populate element
 * @return a new DOM element constructed from supplied values
 * @type HTMLElement 
 */
flyui.genefinder.DefaultRenderer.renderDomElementFromList = function(outerTag, innerTag, list) {

	var outerElem = document.createElement(outerTag);

	// can we defer this to caller?  tbody.appendChild(headerRow);
	
	log("creating outer "+outerTag, "debug");
	for (var i=0; i<list.length; i++) {
		var innerElem = document.createElement(innerTag);
		innerElem.innerHTML = list[i];
		outerElem.appendChild(innerElem);
	}	

	return outerElem;	
}


flyui.genefinder.DefaultRenderer.prototype._renderResultsSummary = function( query, count ) {
	var content = "found ";
	content += count;
	content += " matching gene";
	content += (count == 0 || count > 1) ? "s " : " ";
	content += "from <a href='http://www.flybase.org'>www.flybase.org</a> for query '"+query+"'";
	this._resultsSummaryPane.innerHTML = content;
}


/**
 * TODO doc me
 */
flyui.genefinder.DefaultRenderer.prototype._renderResults = function( genes ) {
	
	// set up overall table structure
	var table = document.createElement("table");
	var thead = document.createElement("thead");
	var tbody = document.createElement("tbody");
	table.appendChild(thead);
	table.appendChild(tbody);

	// set up header row
	var headers = ["symbol", "full name", "annotation ID", "flybase ID", "user selection"];
	var headerRow = flyui.genefinder.DefaultRenderer.renderDomElementFromList("tr", "th", headers); 
	thead.appendChild(headerRow);
	
	for (var i=0; i<genes.length; i++) {
		log("creating result row "+i, "debug");
		var cellContents = this._geneToTableCellContents(genes[i]);
		var row = flyui.genefinder.DefaultRenderer.renderDomElementFromList("tr", "td", cellContents);
		tbody.appendChild(row);
		YAHOO.util.Event.addListener(row, "click", this._userEventHandler._onResultClicked, i);
	}
	
	return table;
}


flyui.genefinder.DefaultRenderer.prototype._geneToTableCellContents = function( gene ) {
	return [
		gene.symbols.join(" / "),
		gene.fullNames.join(" / "),
		gene.annotationSymbols.join(" / "),
		gene.flybaseID,
		"<span class='selector'>[<a href='javascript:void(0)'>select this gene</a>]</span><span class='isselected'>selected</span>"
	];
}


/** 
 * Definition of genefinder widget model.
 */
flyui.genefinder.modelDefinition = {

	properties : [ "STATE", "RESULTS", "ERRORMESSAGE", "QUERY", "SELECTIONINDEX" ],
	
	values : {
		"STATE" : [ "READY", "PENDING", "SERVERERROR", "UNEXPECTEDERROR" ]
	},
	
	initialize : function( data ) {
		data["STATE"] = "READY";
		data["RESULTS"] = null;
		data["ERRORMESSAGE"] = null;
		data["QUERY"] = null;
		data["SELECTIONINDEX"] = null;
	}
}